package com.feizhoutuobang.shapeline.service.impl;

import java.awt.Color;
import java.awt.Font;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.quartz.CronExpression;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig;

import com.feizhoutuobang.shapeline.bo.Article;
import com.feizhoutuobang.shapeline.bo.Book;
import com.feizhoutuobang.shapeline.bo.Delivery;
import com.feizhoutuobang.shapeline.bo.Feed;
import com.feizhoutuobang.shapeline.bo.Image;
import com.feizhoutuobang.shapeline.bo.Paragraph;
import com.feizhoutuobang.shapeline.bo.Resource;
import com.feizhoutuobang.shapeline.bo.Section;
import com.feizhoutuobang.shapeline.bo.Subscription;
import com.feizhoutuobang.shapeline.bo.SubscriptionFeed;
import com.feizhoutuobang.shapeline.bo.User;
import com.feizhoutuobang.shapeline.bo.impl.DeliveryImpl;
import com.feizhoutuobang.shapeline.dao.ArticleDao;
import com.feizhoutuobang.shapeline.dao.DeliveryDao;
import com.feizhoutuobang.shapeline.dao.FeedDao;
import com.feizhoutuobang.shapeline.dao.ImageDao;
import com.feizhoutuobang.shapeline.dao.SubscriptionDao;
import com.feizhoutuobang.shapeline.dao.UserDao;
import com.feizhoutuobang.shapeline.service.ConfigService;
import com.feizhoutuobang.shapeline.service.SubscriptionService;
import com.feizhoutuobang.shapeline.util.Calendar;
import com.feizhoutuobang.shapeline.util.Guid;
import com.feizhoutuobang.shapeline.util.Shell;
import com.feizhoutuobang.shapeline.util.StringParser;
import com.feizhoutuobang.shapeline.util.image.ImageUtils;

import freemarker.template.Template;
import freemarker.template.TemplateException;

public class SubscriptionServiceImpl implements SubscriptionService {

	private static final Logger LOG = Logger.getLogger(SubscriptionServiceImpl.class);
	
	private SubscriptionDao subscriptionDao;
	private FeedDao feedDao;
	private ArticleDao articleDao;
	private ImageDao imageDao;
	private UserDao userDao;
	private DeliveryDao deliveryDao;
	private ConfigService configService;
	private FreeMarkerConfig freeMarkerConfig;
	
	public SubscriptionDao getSubscriptionDao() {
		return subscriptionDao;
	}

	public void setSubscriptionDao(SubscriptionDao subscriptionDao) {
		this.subscriptionDao = subscriptionDao;
	}

	public FeedDao getFeedDao() {
		return feedDao;
	}

	public void setFeedDao(FeedDao feedDao) {
		this.feedDao = feedDao;
	}

	public ArticleDao getArticleDao() {
		return articleDao;
	}

	public void setArticleDao(ArticleDao articleDao) {
		this.articleDao = articleDao;
	}

	public ImageDao getImageDao() {
		return imageDao;
	}

	public void setImageDao(ImageDao imageDao) {
		this.imageDao = imageDao;
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public DeliveryDao getDeliveryDao() {
		return deliveryDao;
	}

	public void setDeliveryDao(DeliveryDao deliveryDao) {
		this.deliveryDao = deliveryDao;
	}

	public ConfigService getConfigService() {
		return configService;
	}

	public void setConfigService(ConfigService configService) {
		this.configService = configService;
	}

	public FreeMarkerConfig getFreeMarkerConfig() {
		return freeMarkerConfig;
	}

	public void setFreeMarkerConfig(FreeMarkerConfig freeMarkerConfig) {
		this.freeMarkerConfig = freeMarkerConfig;
	}

	@Override
	public void makeSubscription() {
		List<Subscription> subscriptions = getSubscriptionDao().getAutoDeliverSubscription();
		Date now = Calendar.now();
		Date today = Calendar.today();
		for (Subscription subscription : subscriptions) {
			try {
				User user = getUserDao().getUserById(subscription.getUserId());
				
				Date lastDeliverTime = subscription.getLastDeliverTime();
				if (lastDeliverTime.compareTo(Calendar.invalidTime()) == 0) {
					lastDeliverTime = today;
				}
				
				CronExpression cr = new CronExpression(subscription.getCronExpression());
				Date deliverTime = cr.getNextValidTimeAfter(lastDeliverTime);
				if (deliverTime.compareTo(now) <= 0) {
					LOG.info("start deliver for subscription, id: " + subscription.getId());
					
					List<SubscriptionFeed> sfs = new ArrayList<SubscriptionFeed>();
					Book book = createBook(sfs, subscription, deliverTime);
					File mobi = storeBook(book, deliverTime);
					
					LOG.debug("book created, path: " + mobi.getCanonicalPath());
					
					//create deliver tasks
					String bookName = StringParser.escapeFileName(book.getTitle() + " [" + book.getTime() + "]");
					Delivery delivery = new DeliveryImpl();
					delivery.setCreateTime(new Date());
					delivery.setDeliverEmail(user.getDeliverEmail());
					delivery.setFileName(bookName);
					delivery.setFilePath(mobi.getCanonicalPath());
					delivery.setId(Guid.newId());
					delivery.setUserId(user.getId());
					getDeliveryDao().insertDelivery(delivery);
					
					//reset next deliver time
					subscription.setLastDeliverTime(deliverTime);
					getSubscriptionDao().updateSubscription(subscription);
					
					for (SubscriptionFeed sf : sfs) {
						getSubscriptionDao().updateLastPublishTime(sf);
					}
				}
			}
			catch (Exception e) {
				LOG.error("error when deliver for subscription, id: " + subscription.getId(), e);
			}
		}
	}
	
	private File storeBook(Book book, Date deliverTime) throws IOException, TemplateException {
		char separator = File.separatorChar;
		File dir = new File(book.getStorePath());
		if (!dir.exists()) {
			dir.mkdir();
		}
		//String bookName = escapeFileName(book.getTitle() + " [" + book.getTime() + "]");
		
		writeFile(book.getStorePath() + separator + "content.opf", book, "epub/content.opf.html");
		writeFile(book.getStorePath() + separator + "index_u" + book.getId() + ".html", book, "epub/index_ux.html");
		writeFile(book.getStorePath() + separator + "toc.ncx", book, "epub/toc.ncx.html");
		
		printCover(book.getStorePath() + separator + "cover.jpg", "epub/cover.jpg", book.getTitle(), book.getTime());
		copyFile(book.getStorePath() + separator + "mastheadImage.jpg", "epub/mastheadImage.jpg");
		copyFile(book.getStorePath() + separator + "stylesheet.css", "epub/stylesheet.css");
		copyFile(book.getStorePath() + separator + "titlepage.xhtml", "epub/titlepage.xhtml");
		
		for (int sectionIndex = 0; sectionIndex < book.getSections().size(); sectionIndex ++) {
			Section section = book.getSections().get(sectionIndex);
			if (section.getParagraphs().size() == 0) {
				continue;
			}
				
			if (sectionIndex > 0) {
				section.setPreviousSection(book.getSections().get(sectionIndex - 1));
			}
			if (sectionIndex < book.getSections().size() - 1) {
				section.setNextSection(book.getSections().get(sectionIndex + 1));
			}
			
			File sectionDir = new File(section.getStorePath());
			if (!sectionDir.exists()) {
				sectionDir.mkdir();
			}
			
			writeFile(section.getStorePath() + separator + "index_u" + section.getId() + ".html", section, "epub/feed_x/index_ux.html");
			
			for (int paragraphIndex = 0; paragraphIndex < section.getParagraphs().size(); paragraphIndex ++) {
				Paragraph paragraph = section.getParagraphs().get(paragraphIndex);
				if (paragraphIndex > 0) {
					paragraph.setPreviousParagraph(section.getParagraphs().get(paragraphIndex - 1));
				}
				if (paragraphIndex < section.getParagraphs().size() - 1) {
					paragraph.setNextParagraph(section.getParagraphs().get(paragraphIndex + 1));
				}
				
				File paragraphDir = new File(paragraph.getStorePath());
				if (!paragraphDir.exists()) {
					paragraphDir.mkdir();
				}
				
				writeFile(paragraph.getStorePath() + separator + "index_u" + paragraph.getId() + ".html", paragraph, "epub/feed_x/article_x/index_ux.html");
				
				for (Resource resource : paragraph.getResources()) {
					File image = resource.getFile();
					File imageDir = new File(resource.getStorePath());
					if (!imageDir.exists()) {
						imageDir.mkdir();
					}
					
					File distImage = new File(resource.getStorePath() + separator + image.getName());
					distImage.createNewFile();
					copyFile(distImage, image);
				}
			}
		}
		
		String exe = getConfigService().getStringValue("converter.application.path");
		String[] cmd = new String[] {exe, dir.getCanonicalPath() + separator + "content.opf"};
		int ret = Shell.execute(cmd);
		
		if (ret != 0) {
			throw new RuntimeException("error when convert mobipocket, path: " + dir.getCanonicalPath());
		}
		
		File r = new File(dir.getCanonicalPath() + separator + "content.mobi");
		if (!r.exists()) {
			throw new RuntimeException("error when generate mobipocket, path: " + dir.getCanonicalPath());
		}
		
		return r;
	}
	
	private void copyFile(File dist, File src) throws IOException {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(src);
			out = new BufferedOutputStream(new FileOutputStream(dist));
			IOUtils.copy(in, out);
			
			out.flush();
		}
		finally {
			try {in.close(); } catch (Exception e) {}
			try {out.close(); } catch (Exception e) {}
		}
	}

	@SuppressWarnings("deprecation")
	private void copyFile(String dist, String src) throws IOException {
		InputStream in = null;
		OutputStream out = null;
		try {
			URL base = Thread.currentThread().getContextClassLoader().getResource("");
			String path = URLDecoder.decode(base.getPath()) + "/" + src;
			in = new BufferedInputStream(new FileInputStream(path));
			
			File distFile = new File(dist);
			out = new BufferedOutputStream(new FileOutputStream(distFile));
			IOUtils.copy(in, out);
			out.flush();
		}
		finally {
			try {in.close(); } catch (Exception e) {}
			try {out.close(); } catch (Exception e) {}
		}
	}
	
	@SuppressWarnings("deprecation")
	private void printCover(String dist, String src, String title, String date) throws IOException {
		URL base = Thread.currentThread().getContextClassLoader().getResource("");
		String path = URLDecoder.decode(base.getPath()) + "/" + src;
		File from = new File(path);
		File to = new File(dist);
		
		ImageUtils.printText(from, to, Color.BLACK, 
				new String[] {title, date}, 
				new String[] {"黑体", "黑体"},
				new int[] {Font.BOLD, Font.BOLD},
				new int[] {30, 30},
				new int[] {200, 500}, 566);
	}

	private void writeFile(String fileName, Object rootMap, String templateName) throws IOException, TemplateException {
		Writer out = null;
		try {
			Template t = getFreeMarkerConfig().getConfiguration().getTemplate(templateName);
			File file = new File(fileName);
			out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8"));
			t.process(rootMap, out);
		}
		finally {
			try {out.close(); } catch (Exception e) {}
		}
	}

	private Book createBook(List<SubscriptionFeed> out, Subscription subscription, Date deliverTime) {
		String siteAddress = getConfigService().getStringValue("site.address");
		String siteName = getConfigService().getStringValue("site.name");
		String siteDescription = getConfigService().getStringValue("site.description");
		String auth = getConfigService().getStringValue("book.auth");
		String contributor = getConfigService().getStringValue("book.contributor");
		String creator = getConfigService().getStringValue("book.creator");
		String publisher = getConfigService().getStringValue("book.publisher");
		
		String bookStorePath = getConfigService().getStringValue("book.store.path");
		bookStorePath += File.separatorChar + Calendar.format(deliverTime, "yyyyMMdd");
		File dir = new File(bookStorePath);
		if (!dir.exists()) {
			dir.mkdir();
		}
		
		int index = 1;
		
		Book book = new Book();
		book.setId(index ++);
		book.setAuth(auth);
		book.setContributor(contributor);
		book.setCreator(creator);
		book.setIdentifier(UUID.randomUUID().toString().toLowerCase());
		book.setPublisher(publisher);
		book.setTimestamp(Calendar.format(deliverTime, "yyyy-MM-dd'T'HH:mm:ss.SSS'000+08:00'"));
		book.setTitle(subscription.getName());
		book.setTime(Calendar.format(deliverTime, "M月d日 E"));
		book.setDescription(book.getTitle() + " [" + book.getTime() + "]" + " - 由" + siteName + "投递" + " [" + siteAddress + "] " + siteDescription);
		book.setStorePath(bookStorePath + File.separatorChar + book.getIdentifier());
		
		List<SubscriptionFeed> sfs = getSubscriptionDao().getFeedsOfSubscription(subscription);
		for (int feedIndex = 0; feedIndex < sfs.size(); feedIndex ++) {
			Feed feed = getFeedDao().getFeedById(sfs.get(feedIndex).getFeedId());
			
			Section section = new Section(book);
			section.setDescription(feed.getDescription());
			section.setId(index ++);
			section.setTitle(feed.getTitle());
			section.setStorePath(book.getStorePath() + File.separatorChar + "feed_" + feedIndex);
			section.setStoreName("feed_" + feedIndex);
			book.getSections().add(section);
			
			List<Article> articles = getArticleDao().getNewArticleOfFeed(feed, sfs.get(feedIndex).getLastPublishTime(), subscription.getMaxItemCount());
			
			for (int articleIndex = 0; articleIndex < articles.size(); articleIndex ++) {
				Article article = articles.get(articleIndex);
				
				if (articleIndex == 0) {
					sfs.get(feedIndex).setLastPublishTime(article.getPublishTime());
					out.add(sfs.get(feedIndex));
				}
				
				Paragraph paragraph = new Paragraph(section);
				paragraph.setContent(article.getContent());
				paragraph.setId(index ++);
				paragraph.setPublishTime(Calendar.format(article.getPublishTime(), "M月d日 E H:m"));
				paragraph.setTitle(article.getTitle());
				paragraph.setStorePath(section.getStorePath() + File.separatorChar + "article_" + articleIndex);
				paragraph.setStoreName("article_" + articleIndex);
				paragraph.setLink(article.getLink());
				section.getParagraphs().add(paragraph);
				
				List<Image> images = getImageDao().getImagesOfArticle(article);
				for (Image image : images) {
					Resource resource = new Resource(paragraph);
					resource.setFile(image.getImage());
					resource.setId(index ++);
					resource.setStorePath(paragraph.getStorePath() + File.separatorChar + "image");
					resource.setStoreName(image.getImage().getName());
					paragraph.getResources().add(resource);
				}
			}
		}
		
		return book;
	}
	
//	private File storeMobi(Book book, Date deliverTime) throws IOException, TemplateException {
//	char separator = File.separatorChar;
//	File dir = new File(book.getStorePath());
//	if (!dir.exists()) {
//		dir.mkdir();
//	}
//	
//	writeFile(book.getStorePath() + separator + escapeFileName(book.getTitle() + " [" + book.getTime() + "]") + ".opf", book, "mobi/content.opf.html");
//	writeFile(book.getStorePath() + separator + "contents.html", book, "mobi/contents.html");
//	writeFile(book.getStorePath() + separator + "nav-contents.ncx", book, "mobi/nav-contents.ncx.html");
//	//writeFile(book.getStorePath() + separator + "index_u" + book.getId() + ".html", book, "mobi/index_ux.html");
//	
//	copyFile(book.getStorePath() + separator + "cover-image.jpg", "mobi/cover.jpg");
//	copyFile(book.getStorePath() + separator + "mastheadImage.jpg", "mobi/mastheadImage.jpg");
//	//copyFile(book.getStorePath() + separator + "stylesheet.css", "mobi/stylesheet.css");
//	
//	for (int sectionIndex = 0; sectionIndex < book.getSections().size(); sectionIndex ++) {
//		Section section = book.getSections().get(sectionIndex);
//		if (sectionIndex > 0) {
//			section.setPreviousSection(book.getSections().get(sectionIndex - 1));
//		}
//		if (sectionIndex < book.getSections().size() - 1) {
//			section.setNextSection(book.getSections().get(sectionIndex + 1));
//		}
//		
//		File sectionDir = new File(section.getStorePath());
//		if (!sectionDir.exists()) {
//			sectionDir.mkdir();
//		}
//		
//		//writeFile(section.getStorePath() + separator + "index_u" + section.getId() + ".html", section, "mobi/feed_x/index_ux.html");
//		
//		for (int paragraphIndex = 0; paragraphIndex < section.getParagraphs().size(); paragraphIndex ++) {
//			Paragraph paragraph = section.getParagraphs().get(paragraphIndex);
//			if (paragraphIndex > 0) {
//				paragraph.setPreviousParagraph(section.getParagraphs().get(paragraphIndex - 1));
//			}
//			if (paragraphIndex < section.getParagraphs().size() - 1) {
//				paragraph.setNextParagraph(section.getParagraphs().get(paragraphIndex + 1));
//			}
//			
//			File paragraphDir = new File(paragraph.getStorePath());
//			if (!paragraphDir.exists()) {
//				paragraphDir.mkdir();
//			}
//			
//			writeFile(paragraph.getStorePath() + separator + "index_u" + paragraph.getId() + ".html", paragraph, "mobi/feed_x/article_x/index_ux.html");
//			
//			for (Resource resource : paragraph.getResources()) {
//				File image = resource.getFile();
//				File imageDir = new File(resource.getStorePath());
//				if (!imageDir.exists()) {
//					imageDir.mkdir();
//				}
//				
//				File distImage = new File(resource.getStorePath() + separator + image.getName());
//				distImage.createNewFile();
//				copyFile(distImage, image);
//			}
//		}
//	}
//	
//	return dir;
//}

}
